package selectors;

import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.Map.Entry;

import strategies.Strategy;

import base.Environment;

import agents.Agent;
import agents.AverageUtility;

import static base.Environment.Configurations.*;

/**
 * Selects the strategy to employ based on a simulated annealing algorithm.
 */
public class AnnealingStrategySelector extends StrategySelector {
	
	private class UnderDogComparator implements Comparator<Strategy> {
		private Agent agent;
		
		public UnderDogComparator(Agent agent) {
			this.agent = agent;
		}
		
		@Override
		public int compare(Strategy s1, Strategy s2) {
			AverageUtility u1 = agent.getStrategies().get(s1);
			AverageUtility u2 = agent.getStrategies().get(s2);
			
			if (u1.getSteps() == u2.getSteps())
				if (u1.getAverageUtility() > u2.getAverageUtility())
					return -1;
				else if (u1.getAverageUtility() < u2.getAverageUtility())
					return 1;
				else
					return 0;
			
			return agent.getStrategies().get(s1).getSteps() -
				   agent.getStrategies().get(s1).getSteps();
		}
	}
	
	
	protected double exp;
	protected double decrease;
	
	
	public AnnealingStrategySelector(Environment env) {
		this.exp      = env.getDouble(INITIAL_EXP);
		this.decrease = env.getDouble(EXP_DECREASE);
	}
	

	@Override
	public Strategy selectStrategy(Agent agent) {
		
		Strategy strategy = null;
		double max = Double.NEGATIVE_INFINITY;
		
		for (Entry<Strategy, AverageUtility> entry : agent.getStrategies().entrySet())
			if (entry.getValue().getAverageUtility() > max) {
				strategy = entry.getKey();
				max = entry.getValue().getAverageUtility();
			}
		
		if (!explore())
			return strategy;
		
		if (agent.getStrategies().entrySet().size() < 2)
			return strategy;
		
		LinkedList<Strategy> list = new LinkedList<Strategy>();
		list.addAll(agent.getStrategies().keySet());
		list.remove(strategy);
		Collections.sort(list, new UnderDogComparator(agent));
		double r = Math.random();
		
		return list.get((int) (r * r * list.size()));
	}
	
	protected boolean explore() {
		boolean t = Math.random() <= Math.exp(exp);
		exp -= decrease;
		return t;
	}
}
